<?php

namespace Yjius\aibigmodel\qianwen;

use Yjius\aibigmodel\AIBigModelBuilderInterface;
use Yjius\common\LoggerHelper;

class QianWen implements AIBigModelBuilderInterface
{
    //实例化代码
    static private $instance;

    private function __construct($params)
    {
        $this->chatUrl = $params['chat_url'] ?? "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions";
        $this->chatModel = $params['chat_model'] ?? "qwq-32b-preview";
        $this->apiKey = $params['api_key'] ?? "";
        if (empty($this->apiKey)) {
            throw new \Exception("TongYiQianWen:api_key不能为空");
        }
        $this->saveLog = $params['save_log'] ?? false;
        if ($this->saveLog) {
            $this->loggerInstance = LoggerHelper::init();
        }
    }

    private function __clone()
    {
    }

    //单例模式
    static public function getInstance($params)
    {
        if (!(self::$instance instanceof self)) {
            self::$instance = new self($params);
        }
        return self::$instance;
    }

    /**
     * 发起一次聊天请求
     *
     * 该函数用于向指定的聊天模型发起一次聊天请求，并返回模型的响应结果
     * 它允许传递自定义的参数来调整聊天的上下文或模型的行为
     *
     * @param array $paramsArray 包含额外聊天参数的数组，如问题文本等
     * @param array $customerParams 包含客户自定义参数的数组，目前未使用
     *
     * @return mixed 模型的响应结果，具体类型取决于返回值
     */
    function chatOnce(array $paramsArray, array $customerParams)
    {
        // 设置请求体
        $data = [
            // 模型列表：https://help.aliyun.com/zh/model-studio/getting-started/models
            "model" => $this->chatModel,
            "messages" => [
                [
                    "role" => "system",
                    "content" => "You are a helpful assistant."
                ],
                [
                    "role" => "user",
                    "content" => "你是谁？"
                ]
            ]
        ];
        // 合并自定义参数到请求体中
        $data = array_merge($data, $paramsArray);
        // 设置非流式响应
        $data["stream"] = false;
        // 发起curl请求
        $res = $this->curlRequest($data);
        // 如果开启了日志保存功能，则记录请求和响应内容
        if ($this->saveLog) {
            //问题结果日志记录
            $this->loggerInstance->write("qianwen_chatOnce_" . $this->apiKey . ".log", json_encode($data, JSON_UNESCAPED_UNICODE));
            $this->loggerInstance->write("qianwen_chatOnce_" . $this->apiKey . ".log", $res);
        }
        // 返回模型的响应结果
        return $res;
    }

    /**
     * 处理单次聊天的结果
     *
     * 该函数的目的是从给定的JSON字符串中提取聊天内容它期望输入的字符串遵循特定的格式，
     * 即包含一个名为'choices'的键，其中第一个元素是一个包含'message'键的数组，
     * 而'message'键又包含一个'content'键，其中实际的聊天内容被存储
     * 如果输入的字符串不符合这个格式，或者在解析过程中发生任何错误，函数将静默地返回null
     *
     * @param string $paramsStr 包含聊天数据的JSON字符串
     * @param array $customerParams 客户端参数数组，本函数中未使用，但可能在将来用于扩展功能
     *
     * @return string|null 返回聊天内容字符串，如果无法提取则返回null
     */
    function chatOnceDealResult(string $paramsStr, array $customerParams)
    {
        try {
            // 将JSON字符串转换为关联数组
            $dataArr = json_decode($paramsStr, true);

            // 提取并返回聊天内容
            return $dataArr['choices'][0]['message']['content'] ?? "";
        } catch (\Exception $e) {
            // 直接返回null，不记录错误信息
            return null;
        }
    }


    /**
     * 执行一次基于Server-Sent Events (SSE)的聊天请求
     *
     * 该函数通过向服务器发送请求并以SSE的形式接收响应，实现与模型的单次聊天交互
     * 它允许通过参数自定义聊天上下文，并支持流式处理服务器响应
     *
     * @param array $paramsArray 包含聊天所需参数的数组，如模型所需的上下文信息
     * @param array $customerParams 客户端自定义参数，目前未使用但保留作为扩展接口
     * @param callable $handlerFunction 用于处理服务器响应的回调函数，接收两个参数：curl资源对象和响应内容
     * @return string 从模型获取的响应内容，以字符串形式返回
     */
    function sseChatOnce(array $paramsArray, callable $handlerFunction, array $customerParams)
    {
        // 设置请求体
        $data = [
            // 模型列表：https://help.aliyun.com/zh/model-studio/getting-started/models
            "model" => $this->chatModel,
            "messages" => [
                [
                    "role" => "system",
                    "content" => "You are a helpful assistant."
                ],
                [
                    "role" => "user",
                    "content" => "你是谁？"
                ]
            ],
        ];
        $data = array_merge($data, $paramsArray);
        $data["stream"] = true;

        // 如果启用了日志记录，保存请求数据到日志
        if ($this->saveLog) {
            //问题结果日志记录
            $this->loggerInstance->write("qianwen_sseChatOnce_" . $this->apiKey . ".log", json_encode($data, JSON_UNESCAPED_UNICODE));
        }

        // 发起SSE请求并处理响应
        $this->sseCurlRequest($data, function ($curl, $response) use (&$responseStr, $handlerFunction) {
            // 如果启用了日志记录，保存响应数据到日志
            if ($this->saveLog) {
                //问题结果日志记录
                $this->loggerInstance->write("qianwen_sseChatOnce_" . $this->apiKey . ".log", $response);
            }

            // 输出响应内容
            $handlerFunction($response);

            // 返回处理的响应长度
            return strlen($response);
        });

        // 返回拼接的响应字符串
        exit();
    }


    private function curlRequest($data)
    {

        // 若没有配置环境变量，请用百炼API Key将下行替换为：$apiKey = "sk-xxx";
        // 设置请求头
        $headers = [
            'Authorization: Bearer ' . $this->apiKey,
            'Content-Type: application/json'
        ];

        // 初始化cURL会话
        $ch = curl_init();
        // 设置cURL选项
        // 设置请求的URL
        curl_setopt($ch, CURLOPT_URL, $this->chatUrl);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);


        // 执行cURL会话
        $response = curl_exec($ch);

        // 检查是否有错误发生
        $errno = curl_errno($ch);       //curl错误码
        $error = curl_error($ch);       //curl错误信息

        if ($errno) {         //curl异常
            $curlError = json_encode(['curl_errno' => $errno, 'curl_error' => $error], JSON_UNESCAPED_UNICODE);
            throw new \Exception("curl异常：" . $curlError);
        }
        // 关闭cURL资源
        curl_close($ch);
        // 关闭cURL资源
        return $response;
    }

    private function sseCurlRequest($params, callable $callback)
    {
        // 设置请求头
        $headers = [
            'Authorization: Bearer ' . $this->apiKey,
            'Content-Type: application/json'
        ];
        // 初始化cURL会话
        $ch = curl_init();
        // 设置cURL选项
        // 设置请求的URL
        curl_setopt($ch, CURLOPT_URL, $this->chatUrl);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        $responseStr = ''; // 存储响应数据
        // 回调函数处理接收到的数据
        curl_setopt($ch, CURLOPT_WRITEFUNCTION, $callback);

        // 执行cURL会话
        curl_exec($ch);

        // 检查是否有错误发生
        $errno = curl_errno($ch);       //curl错误码
        $error = curl_error($ch);       //curl错误信息

        if ($errno) {         //curl异常
            $curlError = json_encode(['curl_errno' => $errno, 'curl_error' => $error], JSON_UNESCAPED_UNICODE);
            throw new \Exception("curl异常：" . $curlError);
        }
        // 关闭cURL资源
        curl_close($ch);
        @ob_end_flush();
        // 关闭cURL资源
        return $responseStr;
    }

}